#include "GameLogger.h"
#include "spdlog/async.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"

extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}

CGameLogger::CGameLogger()
{
}

CGameLogger::~CGameLogger()
{
}

void CGameLogger::startup()
{
    // Customize msg format for all loggers
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    auto daily_file_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>("logs/happy_log.txt", 0, 0);
    spdlog::set_default_logger(std::shared_ptr<spdlog::logger>(new spdlog::logger("default", { console_sink, daily_file_sink })));
    spdlog::set_automatic_registration(true);
    spdlog::set_pattern("[%m/%d %H:%M:%S.%e][T:%t][%^%l%$]%v");
    spdlog::set_level(spdlog::level::debug);
}

void CGameLogger::shutdown()
{
    spdlog::shutdown();
}

void CGameLogger::set_level(spdlog::level::level_enum level)
{
    spdlog::set_level(level);
}

void CGameLogger::debug(const char* tag, const char* where, const char* msg)
{
    if (!m_ignore_map[tag]) {
        spdlog::debug("[{}][{}] {}", tag, where, msg);
    }
}

void CGameLogger::info(const char* tag, const char* where, const char* msg)
{
    if (!m_ignore_map[tag]) {
        spdlog::info("[{}][{}] {}", tag, where, msg);
    }
}

void CGameLogger::warn(const char* tag, const char* where, const char* msg)
{
    if (!m_ignore_map[tag]) {
        spdlog::warn("[{}][{}] {}", tag, where, msg);
    }
}

void CGameLogger::error(const char* tag, const char* where, const char* msg)
{
    if (!m_ignore_map[tag]) {
        spdlog::error("[{}][{}] {}", tag, where, msg);
    }
}

void CGameLogger::critical(const char* tag, const char* where, const char* msg)
{
    if (!m_ignore_map[tag]) {
        spdlog::critical("[{}][{}] {}", tag, where, msg);
    }
}

bool CGameLogger::trace_enable(const char* tag, bool flag)
{
    m_ignore_map[tag] = flag;
    return flag;
}

typedef struct luaL_Enum {
    const char* name;
    int value;
} luaL_Enum;

static void luaL_setenums(lua_State* L, luaL_Enum* p, const char* name) {
    lua_createtable(L, 0, 0);
    while (p->name) {
        lua_pushinteger(L, p->value);
        lua_setfield(L, -2, p->name);
        p++;
    }
    lua_setfield(L, -2, name);
}

static const char* luaL_getwhere(lua_State* L, int level) {
    lua_Debug ar;
    if (lua_getstack(L, 1, &ar)) {  /* check function at level */
        lua_getinfo(L, "Sl", &ar);  /* get info about it */
        if (ar.currentline > 0) {  /* is there info? */
            return lua_pushfstring(L, "%s:%d", ar.short_src, ar.currentline);
        }
    }
    return lua_pushfstring(L, "");  /* else, no information available... */
}

static const char* luaL_getmessage(lua_State* L, int idx) {
    int n = lua_gettop(L);
    lua_getglobal(L, "string");
    lua_getfield(L, -1, "format");
    for (int i = idx + 1; i <= n; i++) {
        lua_pushvalue(L, i);
    }
    lua_pcall(L, n - 1, 1, 0);
    const char* msg = lua_tostring(L, -1);
    lua_pop(L, 2);
    return msg;
}

static int set_level(lua_State* L) {
    int level = (int)luaL_checknumber(L, 1);
    GameLogger::instance().set_level((spdlog::level::level_enum) level);
    return 1;
}

static int debug(lua_State* L) {
    const char* tag = luaL_checkstring(L, 1);
    const char* where = luaL_getwhere(L, 1);
    const char* msg = luaL_getmessage(L, 1);
    GameLogger::instance().debug(tag, where, msg);
    return 1;
}

static int info(lua_State* L) {
    const char* tag = luaL_checkstring(L, 1);
    const char* where = luaL_getwhere(L, 1);
    const char* msg = luaL_getmessage(L, 1);
    GameLogger::instance().info(tag, where, msg);
    return 1;
}

static int warn(lua_State* L) {
    const char* tag = luaL_checkstring(L, 1);
    const char* where = luaL_getwhere(L, 1);
    const char* msg = luaL_getmessage(L, 1);
    GameLogger::instance().warn(tag, where, msg);
    return 1;
}

static int error(lua_State* L) {
    const char* tag = luaL_checkstring(L, 1);
    const char* where = luaL_getwhere(L, 1);
    const char* msg = luaL_getmessage(L, 1);
    GameLogger::instance().error(tag, where, msg);
    return 1;
}

static int critical(lua_State* L) {
    const char* tag = luaL_checkstring(L, 1);
    const char* where = luaL_getwhere(L, 1);
    const char* msg = luaL_getmessage(L, 1);
    GameLogger::instance().critical(tag, where, msg);
    return 1;
}

static int trace_enable(lua_State* L) {
    const char* tag = luaL_checkstring(L, 1);
    bool flag = (bool)luaL_checknumber(L, 2);
    auto res = GameLogger::instance().trace_enable(tag, !flag);
    lua_pushboolean(L, res);
    return 1;
}

/*
** Assumes the table is on top of the stack.
*/
static void set_info(lua_State* L) {
    lua_pushliteral(L, "_COPYRIGHT");
    lua_pushliteral(L, "Copyright (C) 2020-2020 Happy Project");
    lua_rawset(L, -3);

    lua_pushliteral(L, "_DESCRIPTION");
    lua_pushliteral(L, "Happy Supported.");
    lua_rawset(L, -3);

    lua_pushliteral(L, "_VERSION");
    lua_pushliteral(L, "Happy Spdlog 1.0");
    lua_rawset(L, -3);
}

static luaL_Enum enums[] = {
  {"trace",    spdlog::level::trace},
  {"debug",    spdlog::level::debug},
  {"info",     spdlog::level::info},
  {"warn",     spdlog::level::warn},
  {"err",      spdlog::level::err},
  {"critical", spdlog::level::critical},
  {"off",      spdlog::level::off},
  {NULL, 0}
};

static const struct luaL_Reg funcs[] = {
  {"set_level", set_level},
  {"debug", debug},
  {"info", info},
  {"warn", warn},
  {"error", error},
  {"critical", critical},
  {"trace_enable", trace_enable},
  {NULL, NULL},
};

extern "C" {
    int luaopen_spdlog(lua_State* L) {
        lua_newtable(L);
        luaL_setenums(L, enums, "level");
        luaL_setfuncs(L, funcs, 0);
        set_info(L);
        return 1;
    }
}
